package org.sunrise.game.rpc.function;

import org.sunrise.game.common.log.LogCore;
import org.sunrise.game.common.network.client.BaseClientManager;
import org.sunrise.game.common.network.server.BaseServerManager;
import org.sunrise.game.common.network.server.ConnectionManger;
import org.sunrise.game.rpc.node.RpcNodeManager;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 使用rpc需创建RpcNode，一个进程仅能创建一个RpcNode
 * 提供两种发送模式：1.群发给所有其他节点 2.随机挑选一个发送
 * 注意：如果call调用的方法在当前节点注册过，则一定会由当前节点处理
 */
public class RpcFunction {

    public enum RpcCallType {
        SendRandom,  // 随机发送
        SendAll      // 发送给所有节点
    }

    private RpcCallType callType = RpcCallType.SendRandom;
    public static String nodeId; //当前rpc服务器的节点id
    private final List<Call> calls = new ArrayList<>();
    public static final Map<Integer, List<String>> callIdNodes = new ConcurrentHashMap<>();

    public RpcFunction() {
    }

    public RpcFunction(RpcCallType callType) {
        this.callType = callType;
    }

    /**
     * 创建调用器，默认为随机发送
     */
    public static RpcFunction newInstance() {
        return new RpcFunction();
    }

    /**
     * 创建调用器，指定发送类型
     */
    public static RpcFunction newInstance(RpcCallType type) {
        return new RpcFunction(type);
    }

    /**
     * 发起一次调用
     */
    public boolean call(int id, Object... params) {
        if (params.length % 2 != 0) {
            return false;
        }
        var callNode = callIdNodes.get(id);
        // 没有任何节点提供此方法
        if (callNode == null || callNode.isEmpty()) {
            return false;
        }

        // 发给所有注册此方法的远端
        // 每个call使用同一个消息id，回调函数只调用一次
        if (callType == RpcCallType.SendAll) {
            long msgId = 0L;
            for (int i = 0; i < callNode.size(); i++) {
                Call call;
                if (i == 0) {
                    call = new Call(getCurNodeByServerNode(callNode.getFirst()), id);
                    call.setType(CallType.Call.ordinal());
                    call.setData(params);
                    calls.add(call);
                    msgId = call.getMessageId();
                } else {
                    call = new Call(getCurNodeByServerNode(callNode.get(i)), id, msgId);
                    call.setType(CallType.Call.ordinal());
                    call.setData(params);
                    calls.add(call);
                }
                if (callNode.get(i).equals(nodeId)) {
                    //当前节点有这个方法，放入自身消息队列
                    call.setNodeId(nodeId);
                    BaseServerManager.recvFromClient(nodeId, call);
                } else {
                    BaseClientManager.sendToServer(call);
                }
                LogCore.RpcClient.debug("rpc call, callId = {}, messageId = { {} }, params = {}", call.getRpcId(), call.getMessageId(), params);
            }
        } else if (callType == RpcCallType.SendRandom) {
            if (nodeId != null) {
                for (String s : callNode) {
                    //当前节点有这个方法，放入自身消息队列
                    if (nodeId.equals(s)) {
                        Call call = new Call(nodeId, id);
                        call.setType(CallType.Call.ordinal());
                        call.setData(params);
                        calls.add(call);
                        BaseServerManager.recvFromClient(nodeId, call);
                        LogCore.RpcClient.debug("rpc call, callId = {}, messageId = { {} }, params = {}", call.getRpcId(), call.getMessageId(), params);
                        return true;
                    }
                }
            }

            // 随机挑选一个远端进行发送
            Random random = new Random();
            int index = random.nextInt(callNode.size());
            Call call = new Call(getCurNodeByServerNode(callNode.get(index)), id);
            call.setType(CallType.Call.ordinal());
            call.setData(params);
            calls.add(call);
            BaseClientManager.sendToServer(call);
            LogCore.RpcClient.debug("rpc call, callId = {}, messageId = { {} }, params = {}", call.getRpcId(), call.getMessageId(), params);
        }
        return true;
    }

    /**
     * rpcClient发起调用之后，注册回调函数
     */
    public void listenResult(Callback<RpcResult> callback, Object... contexts) {
        if (calls.isEmpty()) {
            return;
        }
        Call last = calls.getLast();
        CallResult callResult = new CallResult(callback, contexts);
        if (last.getNodeId() != null) {
            RpcManager.registerCallback(last.getMessageId(), callResult);
        } else {
            callResult.getRpcResult().setResult(ErrorType.RPC_NOT_REGISTER);
            callback.process(callResult.getRpcResult());
        }
    }

    /**
     * 监听返回值后，手动设置超时时间，不设置默认为10秒
     */
    public void setTimeOut(long mills) {
        if (calls.isEmpty()) {
            return;
        }
        Call last = calls.getLast();
        RpcManager.setTimeOut(last.getMessageId(), mills);
    }

    /**
     * rpcServer发起一次更新，推送给指定node的rpcClient，通知自身所管理的rpc方法
     */
    public void update(String connectNode, List<Integer> callIds) {
        if (!ConnectionManger.isConnectExist(connectNode)) {
            return;
        }
        if (nodeId == null) {
            return;
        }
        Call call = new Call(nodeId);
        call.setType(CallType.Update.ordinal());
        call.setMsg(callIds);
        call.setToNodeId(connectNode);
        calls.add(call);
        BaseServerManager.sendToClient(call);
    }

    /**
     * rpc客户端收到rpc服务器发来的rpc列表
     */
    @SuppressWarnings("unchecked")
    public static void onUpdate(Call call) {
        List<Integer> callIds = (List<Integer>) call.getMsg();
        if (callIds == null) {
            return;
        }
        for (int callId : callIds) {
            List<String> list = RpcFunction.callIdNodes.get(callId);
            if (list == null) {
                list = new ArrayList<>();
            }
            list.add(call.getNodeId());
            RpcFunction.callIdNodes.put(callId, list);
        }
    }

    private static String getCurNodeByServerNode(String serverNode) {
        return RpcNodeManager.getClientNodeIdByServerNodeId(serverNode);
    }
}
